package cn.zzz.openplatform.amap;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import cn.zzz.openplatform.amap.constant.AmapUrlConstant;
import cn.zzz.openplatform.amap.model.AmapCredentials;
import cn.zzz.openplatform.amap.model.AmapOpenApiRequest;
import cn.zzz.openplatform.amap.model.AmapOpenApiResponse;
import cn.zzz.openplatform.amap.model.convert.AmapCoordinateConvertRequest;
import cn.zzz.openplatform.amap.model.convert.AmapCoordinateConvertResponse;
import cn.zzz.openplatform.amap.model.direction.*;
import cn.zzz.openplatform.amap.model.district.AmapDistrictRequest;
import cn.zzz.openplatform.amap.model.district.AmapDistrictResponse;
import cn.zzz.openplatform.amap.model.event.AmapEventQueryRequest;
import cn.zzz.openplatform.amap.model.event.AmapEventQueryResponse;
import cn.zzz.openplatform.amap.model.geocode.AmapGeoCodeRequest;
import cn.zzz.openplatform.amap.model.geocode.AmapGeoCodeResponse;
import cn.zzz.openplatform.amap.model.geocode.AmapReverseGeoCodeRequest;
import cn.zzz.openplatform.amap.model.geocode.AmapReverseGeoCodeResponse;
import cn.zzz.openplatform.amap.model.ip.AmapIpLocationRequest;
import cn.zzz.openplatform.amap.model.ip.AmapIpLocationResponse;
import cn.zzz.openplatform.amap.model.weather.AmapWeatherInfoAllRequest;
import cn.zzz.openplatform.amap.model.weather.AmapWeatherInfoAllResponse;
import cn.zzz.openplatform.amap.model.weather.AmapWeatherInfoBaseRequest;
import cn.zzz.openplatform.amap.model.weather.AmapWeatherInfoBaseResponse;
import cn.zzz.openplatform.common.selector.CredentialSelector;
import cn.zzz.openplatform.common.selector.RoundRobinSelector;

import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * 高德地图开放平台服务
 * todo 后面看看怎么优化成多key轮训的
 */
public class AmapOpenApiService {

    private final List<AmapCredentials> credentialPool;

    private final CredentialSelector selector;

    public AmapOpenApiService(String key) {
        this(Collections.singletonList(new AmapCredentials(key)));
    }

    public AmapOpenApiService(String key, String sig) {
        this(Collections.singletonList(new AmapCredentials(key, sig)));
    }

    public AmapOpenApiService(AmapCredentials credentials) {
        this(Collections.singletonList(credentials));
    }

    public AmapOpenApiService(List<AmapCredentials> credentialPool) {
        this(credentialPool, new RoundRobinSelector());
    }

    public AmapOpenApiService(List<AmapCredentials> credentialPool, CredentialSelector selector) {
        this.credentialPool = credentialPool;
        this.selector = selector;
    }

    /**
     * 查询实况天气
     *
     * @param city
     * @return
     */
    public AmapWeatherInfoBaseResponse weatherInfoBase(String city) {
        AmapWeatherInfoBaseRequest request = new AmapWeatherInfoBaseRequest(city);
        return execute(request);
    }

    /**
     * 查询预报天气
     *
     * @param city
     * @return
     */
    public AmapWeatherInfoAllResponse weatherInfoAll(String city) {
        AmapWeatherInfoAllRequest request = new AmapWeatherInfoAllRequest(city);
        return execute(request);
    }

    /**
     * 获取地理编码
     *
     * @param address 结构化地址
     * @param city    指定城市
     * @return 地理编码响应结果
     */
    public AmapGeoCodeResponse getGeocode(String address, String city) {
        AmapGeoCodeRequest request = AmapGeoCodeRequest.builder()
                .address(address)
                .city(city)
                .build();
        return getAmapGeoCodeResponse(request);
    }

    /**
     * 获取地理编码
     *
     * @param request 地理编码请求参数
     * @return 地理编码响应结果
     */
    private AmapGeoCodeResponse getAmapGeoCodeResponse(AmapGeoCodeRequest request) {
        return execute(request);
    }

    /**
     * 逆地理编码查询
     *
     * @param lat 经度
     * @param lng 维度
     * @return 逆地理编码响应数据
     */
    public AmapReverseGeoCodeResponse reverseGeocode(double lat, double lng) {
        AmapReverseGeoCodeRequest request = AmapReverseGeoCodeRequest.builder()
                .location(lat + "," + lng)
                .build();
        return reverseGeocode(request);
    }

    /**
     * 逆地理编码查询
     *
     * @param request 逆地理编码请求参数
     * @return 逆地理编码响应数据
     */
    private AmapReverseGeoCodeResponse reverseGeocode(AmapReverseGeoCodeRequest request) {
        return execute(request);
    }

    /**
     * 获取步行路径规划
     *
     * @param origin      出发点，格式为lon,lat（经度,纬度）
     * @param destination 目的地，格式为lon,lat（经度,纬度）
     * @return 步行路径规划响应数据
     */
    public AmapWalkingDirectionResponse getWalkingDirection(String origin, String destination) {
        AmapWalkingDirectionRequest request = AmapWalkingDirectionRequest.builder()
                .origin(origin)
                .destination(destination)
                .build();
        return getWalkingDirection(request);
    }

    /**
     * 获取步行路径规划
     *
     * @param request 步行路径规划请求参数
     * @return 步行路径规划响应数据
     */
    public AmapWalkingDirectionResponse getWalkingDirection(AmapWalkingDirectionRequest request) {
        return execute(request);
    }

    /**
     * 获取公交路径规划
     *
     * @param origin      出发点，格式为lon,lat（经度,纬度）
     * @param destination 目的地，格式为lon,lat（经度,纬度）
     * @param city        城市/跨城规划时的起点城市
     * @return 公交路径规划响应数据
     */
    public AmapTransitIntegratedDirectionResponse getTransitIntegratedDirection(String origin, String destination, String city) {
        AmapTransitIntegratedDirectionRequest request = AmapTransitIntegratedDirectionRequest.builder()
                .origin(origin)
                .destination(destination)
                .city(city)
                .build();
        return getTransitIntegratedDirection(request);
    }

    /**
     * 获取公交路径规划
     *
     * @param request 公交路径规划请求参数
     * @return 公交路径规划响应数据
     */
    public AmapTransitIntegratedDirectionResponse getTransitIntegratedDirection(AmapTransitIntegratedDirectionRequest request) {
        return execute(request);
    }

    /**
     * 获取驾车路径规划
     *
     * @param origin      出发点，格式为lon,lat（经度,纬度）
     * @param destination 目的地，格式为lon,lat（经度,纬度）
     * @return 驾车路径规划响应数据
     */
    public AmapDrivingDirectionResponse getDrivingDirection(String origin, String destination) {
        AmapDrivingDirectionRequest request = AmapDrivingDirectionRequest.builder()
                .origin(origin)
                .destination(destination)
                .build();
        return getDrivingDirection(request);
    }

    /**
     * 获取驾车路径规划
     *
     * @param request 驾车路径规划请求参数
     * @return 驾车路径规划响应数据
     */
    private AmapDrivingDirectionResponse getDrivingDirection(AmapDrivingDirectionRequest request) {
        return execute(request);
    }

    /**
     * 获取骑行路径规划
     *
     * @param origin      出发点，格式为lon,lat（经度,纬度）
     * @param destination 目的地，格式为lon,lat（经度,纬度）
     * @return 骑行路径规划响应数据
     */
    public AmapBicyclingDirectionResponse getBicyclingDirection(String origin, String destination) {
        AmapBicyclingDirectionRequest request = AmapBicyclingDirectionRequest.builder()
                .origin(origin)
                .destination(destination)
                .build();
        return getBicyclingDirection(request);
    }

    /**
     * 获取骑行路径规划
     *
     * @param request 骑行路径规划请求参数
     * @return 骑行路径规划响应数据
     */
    private AmapBicyclingDirectionResponse getBicyclingDirection(AmapBicyclingDirectionRequest request) {
        return execute(request);
    }

    /**
     * 获取距离测量
     *
     * @param origins     出发点，格式为lon,lat（经度,纬度）用“|”分隔多个坐标对
     * @param destination 目的地，格式为lon,lat（经度,纬度）
     * @return 距离测量响应数据
     */
    public AmapDistanceResponse getDistanceMeasurement(String origins, String destination) {
        AmapDistanceRequest request = AmapDistanceRequest.builder()
                .origins(origins)
                .destination(destination)
                .build();
        return getDistanceMeasurement(request);
    }

    /**
     * 获取距离测量
     *
     * @param request 距离测量请求参数
     * @return 距离测量响应数据
     */
    private AmapDistanceResponse getDistanceMeasurement(AmapDistanceRequest request) {
        return execute(request);
    }

    /**
     * 行政区域查询
     *
     * @param keywords    查询关键字
     * @param subDistrict 子级行政区
     * @return 行政区域查询响应数据
     */
    public AmapDistrictResponse getDistrictQuery(String keywords, Integer subDistrict) {
        AmapDistrictRequest request = AmapDistrictRequest.builder()
                .keywords(keywords)
                .subDistrict(subDistrict)
                .build();
        return getDistrictQuery(request);
    }

    /**
     * 行政区域查询
     *
     * @param request 行政区域查询请求参数
     * @return 行政区域查询响应数据
     */
    private AmapDistrictResponse getDistrictQuery(AmapDistrictRequest request) {
        return execute(request);
    }

    /**
     * IP定位查询
     *
     * @param request IP定位请求参数
     * @return IP定位响应数据
     */
    public AmapIpLocationResponse getIpLocation(AmapIpLocationRequest request) {
        return execute(request);
    }

    /**
     * 坐标转换
     *
     * @param request 坐标转换请求参数
     * @return 坐标转换响应数据
     */
    public AmapCoordinateConvertResponse convertCoordinate(AmapCoordinateConvertRequest request) {
        return execute(request);
    }

    /**
     * 交通事件查询
     *
     * @param request 交通事件查询请求参数
     * @return 交通事件查询响应数据
     */
    public AmapEventQueryResponse queryEvent(AmapEventQueryRequest request) {
        return execute(request);
    }

    private <T extends AmapOpenApiResponse> T execute(AmapOpenApiRequest<T> request) {
        Map<String, Object> paramMap = buildParams(request);
        HttpRequest httpRequest = HttpRequest.get(AmapUrlConstant.BASE_URL + request.getUrl()).form(paramMap);
        T result;
        try (HttpResponse execute = httpRequest.execute()) {
            result = JSONUtil.toBean(execute.body(), request.getResponseClass());
        }
        return result;
    }

    private <T extends AmapOpenApiResponse> Map<String, Object> buildParams(AmapOpenApiRequest<T> request) {
        AmapCredentials credentials = selector.select(credentialPool);
        System.out.println(credentials);
        Map<String, Object> params = request.buildParams();
        params.put("key", credentials.getKey());
        if (credentials.getSig() != null) {
            params.put("sig", credentials.getSig());
        }
        return params;
    }
}
